=begin
 *
 * File: sif01_definitions.rb
 * S-expression Interpreter Framework (SIF).
 * Version 1.0
 *
 * SIF01: A very simple functional language.
 * Supported Special Forms: quote, define, if, and fn.
 * Supported Primitive Procedures: null?, first, rest, and cons
 *
 * Copyright (C) 2006-2008 by Ariel Ortiz <ariel.ortiz@itesm.mx>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
=end

require 'generator'
require 'sif'

#--------------------------------------------------------------------
class QuoteNode < Node

  special_form 'quote'

  def initialize(args)
    if args.length == 1
      @value = args[0]
    else
      raise InterpreterError.new('quote takes one argument')
    end
  end

  def interpret(context)
    return @value
  end

end

#--------------------------------------------------------------------
class DefineNode < Node

  special_form 'define'

  def initialize(args)
    if args.length == 2
      if args[0].kind_of?(Symbol)
        @var_name = args[0]
        @var_value = Node.build(args[1])
      else
        raise InterpreterError.new(
          "Invalid variable name in define: '#{args[0]}'")
      end
    else
      raise InterpreterError.new('define takes two arguments')
    end
  end

  def interpret(context)
    context[@var_name] = @var_value.interpret(context)
    return nil
  end

end

#--------------------------------------------------------------------
class IfNode < Node

  special_form 'if'

  def initialize(args)
    if args.length == 3
      @condition = Node.build(args[0])
      @consequent = Node.build(args[1])
      @alternative = Node.build(args[2])
    else
      raise InterpreterError.new('if takes three arguments')
    end
  end

  def interpret(context)
    if @condition.interpret(context) != []
      return @consequent.interpret(context)
    else
      return @alternative.interpret(context)
    end
  end

end

#--------------------------------------------------------------------
class FnNode < Node

  special_form 'fn'

  def initialize(args)
    if args.length != 2
      raise InterpreterError.new('fn takes two arguments')
    elsif !args.first.kind_of?(Symbol)
      if args.first.kind_of?(Array)
        if (args.first.select {|x| x.kind_of?(Symbol)}).uniq.length \
          != args.first.length
          raise InterpreterError.new(
            'fn parameters must be unique and valid names')
        end
      else
        raise InterpreterError.new(
          'fn argument to lambda must be a symbol or a list')
      end
    end
    @params = args.first
    @body = Node.build(args[1])
  end

  def interpret(context)
    return StaticScopeProcedure.new(context, @params, @body)
  end

end

#--------------------------------------------------------------------
def extend_context(params, args, context)
  r = context.dup
  if params.kind_of?(Symbol)
    r[params] = args
  else
    if args.length != params.length
      raise InterpreterError.new(
        "number of parameters and arguments don't match")
    else
      SyncEnumerator.new(params, args).each do |key, value|
        r[key] = value
      end
    end
  end
  return r
end

#--------------------------------------------------------------------
class StaticScopeProcedure

  def initialize(context, params, body)
    @context = context
    @params = params
    @body = body
  end

  def call(args, context)
    return @body.interpret(extend_context(@params, args, @context))
  end

  def to_sexp
    return "<static scope procedure>"
  end

end

#--------------------------------------------------------------------
Primitives.include('null?') do |args|
  if args.length != 1
    raise InterpreterError.new('null? expects one argument')
  elsif args.first == []
    :t
  else
    []
  end
end

#--------------------------------------------------------------------
Primitives.include('first') do |args|
  if args.length != 1
    raise InterpreterError.new('first expects one argument')
  elsif !args.first.kind_of?(Array) || args.first.empty?
    raise InterpreterError.new('first expects a non-empty list')
  else
    args.first.first
  end
end

#--------------------------------------------------------------------
Primitives.include('rest') do |args|
  if args.length != 1
    raise InterpreterError.new('rest expects one argument')
  elsif !args.first.kind_of?(Array) || args.first.empty?
    raise InterpreterError.new('rest expects a non-empty list')
  else
    args.first.rest
  end
end

#--------------------------------------------------------------------
Primitives.include('cons') do |args|
  if args.length != 2
    raise InterpreterError.new('cons expects two arguments')
  elsif !args[1].kind_of?(Array)
    raise InterpreterError.new('second argument to cons must be a list')
  else
    [args.first] + args[1]
  end
end
